שלטו בתוכנות ביניים של FastAPI מהיסוד. מדריך מעמיק זה מכסה תוכנות ביניים מותאמות אישית, אימות, רישום, טיפול בשגיאות ושיטות עבודה מומלצות לבניית ממשקי API חזקים.
Python FastAPI Middleware: מדריך מקיף לעיבוד בקשות ותגובות
בעולם פיתוח האתרים המודרני, ביצועים, אבטחה ויכולת תחזוקה הם בעלי חשיבות עליונה. מסגרת FastAPI של פייתון צברה במהירות פופולריות בזכות המהירות המדהימה שלה והתכונות הידידותיות למפתחים. אחת התכונות החזקות אך לעיתים קרובות לא מובנות שלה היא middleware. תוכנת ביניים פועלת כקישור חיוני בשרשרת עיבוד הבקשות והתגובות, ומאפשרת למפתחים להריץ קוד, לשנות נתונים ולאכוף כללים לפני שבקשה מגיעה ליעדה או לפני שתגובה נשלחת חזרה ללקוח.
מדריך מקיף זה מיועד לקהל עולמי של מפתחים, החל מאלה שרק מתחילים עם FastAPI ועד לאנשי מקצוע מנוסים המעוניינים להעמיק את הבנתם. נחקור את מושגי הליבה של תוכנות ביניים, נדגים כיצד לבנות פתרונות מותאמים אישית ונעבור על מקרי שימוש מעשיים מהעולם האמיתי. בסופו של דבר, תהיו מצוידים למנף תוכנות ביניים לבניית ממשקי API חזקים, מאובטחים ויעילים יותר.
מהי תוכנת ביניים בהקשר של מסגרות אינטרנט?
לפני שנצלול לקוד, חשוב להבין את הרעיון. תארו לעצמכם את מחזור הבקשה-תגובה של האפליקציה שלכם כצינור או פס ייצור. כאשר לקוח שולח בקשה ל-API שלכם, היא לא סתם פוגעת באופן מיידי בלוגיקת נקודת הקצה שלכם. במקום זאת, היא עוברת סדרה של שלבי עיבוד. באופן דומה, כאשר נקודת הקצה שלכם יוצרת תגובה, היא נוסעת חזרה דרך השלבים האלה לפני שהיא מגיעה ללקוח. רכיבי תוכנת ביניים הם בדיוק השלבים האלה בצינור.
אנלוגיה פופולרית היא מודל הבצל. הליבה של הבצל היא הלוגיקה העסקית של האפליקציה שלכם (נקודת הקצה). כל שכבה של הבצל המקיפה את הליבה היא חלק מתוכנת הביניים. בקשה חייבת להתקלף דרך כל שכבה חיצונית כדי להגיע לליבה, והתגובה נוסעת חזרה דרך אותן שכבות. כל שכבה יכולה לבדוק ולשנות את הבקשה בדרכה פנימה ואת התגובה בדרכה החוצה.
במהותה, תוכנת ביניים היא פונקציה או מחלקה שיש לה גישה לאובייקט הבקשה, לאובייקט התגובה ולתוכנת הביניים הבאה במחזור הבקשה-תגובה של האפליקציה. המטרות העיקריות שלה כוללות:
- ביצוע קוד: בצע פעולות עבור כל בקשה נכנסת, כגון רישום או ניטור ביצועים.
- שינוי הבקשה והתגובה: הוספת כותרות, דחיסת גופי תגובה או המרת פורמטים של נתונים.
- קיצור המחזור: סיים את מחזור הבקשה-תגובה מוקדם. לדוגמה, תוכנת ביניים לאימות יכולה לחסום בקשה לא מאומתת לפני שהיא מגיעה אי פעם לנקודת הקצה המיועדת.
- ניהול דאגות גלובליות: טיפול בדאגות חוצות כמו טיפול בשגיאות, CORS (שיתוף משאבים בין מקורות) וניהול сеsיה במקום מרכזי.
FastAPI בנויה על גבי ערכת הכלים Starlette, המספקת יישום חזק של תקן ASGI (ממשק שער שרת אסינכרוני). תוכנת ביניים היא מושג בסיסי ב-ASGI, מה שהופך אותה לאזרח מן השורה במערכת האקולוגית של FastAPI.
הצורה הפשוטה ביותר: FastAPI Middleware עם מעצב
FastAPI מספקת דרך פשוטה להוסיף תוכנת ביניים באמצעות המעצב @app.middleware("http"). זה מושלם עבור לוגיקה פשוטה ועצמאית שצריכה לפעול עבור כל בקשת HTTP.
בואו ניצור דוגמה קלאסית: תוכנת ביניים לחישוב זמן העיבוד עבור כל בקשה ולהוסיף אותה לכותרות התגובה. זה שימושי להפליא לניטור ביצועים.
דוגמה: תוכנת ביניים לזמן עיבוד
ראשית, ודאו שמותקנים לכם FastAPI ושרת ASGI כמו Uvicorn:
pip install fastapi uvicorn
עכשיו, בואו נכתוב את הקוד בקובץ בשם main.py:
import time
from fastapi import FastAPI, Request
app = FastAPI()
# הגדר את פונקציית תוכנת הביניים
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
# רשום את זמן ההתחלה כאשר הבקשה מגיעה
start_time = time.time()
# המשך לתוכנת הביניים הבאה או לנקודת הקצה
response = await call_next(request)
# חשב את זמן העיבוד
process_time = time.time() - start_time
# הוסף את הכותרת המותאמת אישית לתגובה
response.headers["X-Process-Time"] = str(process_time)
return response
@app.get("/")
async def root():
# הדמיית עבודה מסוימת
time.sleep(0.5)
return {"message": "Hello, World!"}
כדי להריץ את האפליקציה הזו, השתמשו בפקודה:
uvicorn main:app --reload
עכשיו, אם אתם שולחים בקשה אל http://127.0.0.1:8000 באמצעות כלי כמו cURL או לקוח API כמו Postman, תראו כותרת חדשה בתגובה, X-Process-Time, עם ערך של בערך 0.5 שניות.
פירוק הקוד:
@app.middleware("http"): מעצב זה רושם את הפונקציה שלנו כחלק מתוכנת ביניים HTTP.async def add_process_time_header(request: Request, call_next):: פונקציית תוכנת הביניים חייבת להיות אסינכרונית. היא מקבלת את אובייקטRequestהנכנס ואת הפונקציה המיוחדת,call_next.response = await call_next(request): זהו הקו החשוב ביותר.call_nextמעביר את הבקשה לשלב הבא בצינור (או תוכנת ביניים אחרת או פעולת הנתיב בפועל). עליכם `await` לקריאה הזו. התוצאה היא אובייקטResponseשנוצר על ידי נקודת הקצה.response.headers[...] = ...: לאחר קבלת התגובה מנקודת הקצה, אנו יכולים לשנות אותה, במקרה זה, על ידי הוספת כותרת מותאמת אישית.return response: לבסוף, התגובה שהשתנתה מוחזרת כדי להישלח ללקוח.
יצירת תוכנת ביניים מותאמת אישית משלכם עם מחלקות
בעוד שהגישה של המעצב היא פשוטה, היא יכולה להפוך למגבילה עבור תרחישים מורכבים יותר, במיוחד כאשר תוכנת הביניים שלכם דורשת תצורה או צריכה לנהל מצב פנימי כלשהו. במקרים אלה, FastAPI (באמצעות Starlette) תומכת בתוכנת ביניים מבוססת מחלקות באמצעות BaseHTTPMiddleware.
גישה מבוססת מחלקות מציעה מבנה טוב יותר, מאפשרת הזרקת תלות בבונה שלה ובדרך כלל קלה יותר לתחזוקה עבור לוגיקה מורכבת. לוגיקת הליבה שוכנת בשיטת dispatch אסינכרונית.
דוגמה: תוכנת ביניים לאימות מפתח API מבוססת מחלקות
בואו נבנה תוכנת ביניים מעשית יותר המאבטחת את ה-API שלנו. היא תבדוק כותרת ספציפית, X-API-Key, ואם המפתח אינו קיים או אינו חוקי, היא תחזיר מיד תגובת שגיאה 403 Forbidden. זוהי דוגמה ל"קיצור" הבקשה.
ב-main.py:
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
from starlette.responses import Response
# רשימה של מפתחות API חוקיים. באפליקציה אמיתית, זה יגיע ממסד נתונים או מכספת מאובטחת.
VALID_API_KEYS = ["my-super-secret-key", "another-valid-key"]
class APIKeyMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
api_key = request.headers.get("X-API-Key")
if api_key not in VALID_API_KEYS:
# קצר את הבקשה והחזר תגובת שגיאה
return JSONResponse(
status_code=403,
content={"detail": "Forbidden: Invalid or missing API Key"}
)
# אם המפתח חוקי, המשך בבקשה
response = await call_next(request)
return response
app = FastAPI()
# הוסף את תוכנת הביניים לאפליקציה
app.add_middleware(APIKeyMiddleware)
@app.get("/")
async def root():
return {"message": "Welcome to the secure zone!"}
עכשיו, כאשר אתם מריצים את האפליקציה הזו:
- בקשה ללא הכותרת
X-API-Key(או עם ערך שגוי) תקבל קוד סטטוס 403 והודעת שגיאת JSON. - בקשה עם הכותרת
X-API-Key: my-super-secret-keyתצליח ותקבל את תגובת 200 OK.
דפוס זה חזק במיוחד. קוד נקודת הקצה ב-/ לא צריך לדעת דבר על אימות מפתח API; הדאגה הזו מופרדת לחלוטין לשכבת תוכנת הביניים.
מקרי שימוש נפוצים וחזקים עבור תוכנת ביניים
תוכנת ביניים היא הכלי המושלם לטיפול בדאגות חוצות. בואו נחקור כמה ממקרי השימוש הנפוצים והמשפיעים ביותר.
1. רישום מרכזי
רישום מקיף הוא חיוני עבור יישומי ייצור. תוכנת ביניים מאפשרת לכם ליצור נקודה אחת שבה אתם רושמים מידע קריטי על כל בקשה והתגובה המתאימה שלה.
דוגמה לתוכנת ביניים לרישום:
import logging
from fastapi import FastAPI, Request
import time
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
app = FastAPI()
@app.middleware("http")
async def logging_middleware(request: Request, call_next):
start_time = time.time()
# רשום את פרטי הבקשה
logger.info(f"Incoming request: {request.method} {request.url.path}")
response = await call_next(request)
process_time = time.time() - start_time
# רשום את פרטי התגובה
logger.info(f"Response status: {response.status_code} | Process time: {process_time:.4f}s")
return response
תוכנת ביניים זו רושמת את שיטת הבקשה ואת הנתיב שלה בדרכה פנימה, ואת קוד הסטטוס של התגובה ואת סך זמן העיבוד שלה בדרכה החוצה. זה מספק נראות שלא תסולא בפז בתעבורה של האפליקציה שלכם.
2. טיפול בשגיאות גלובלי
כברירת מחדל, חריגה לא מטופלת בקוד שלכם תגרום לשגיאת 500 Internal Server Error, שעלולה לחשוף עקבות מחסנית ופרטי יישום ללקוח. תוכנת ביניים לטיפול בשגיאות גלובלי יכולה לתפוס את כל החריגות, לרשום אותן לסקירה פנימית ולהחזיר תגובת שגיאה סטנדרטית וידידותית למשתמש.
דוגמה לתוכנת ביניים לטיפול בשגיאות:
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
import logging
logger = logging.getLogger(__name__)
app = FastAPI()
@app.middleware("http")
async def error_handling_middleware(request: Request, call_next):
try:
return await call_next(request)
except Exception as e:
logger.error(f"An unhandled error occurred: {e}", exc_info=True)
return JSONResponse(
status_code=500,
content={"detail": "An internal server error occurred. Please try again later."}
)
@app.get("/error")
async def cause_error():
return 1 / 0 # זה יעלה ZeroDivisionError
עם תוכנת ביניים זו, בקשה ל-/error כבר לא תקרוס את השרת או תחשוף את עקבות המחסנית. במקום זאת, היא תחזיר בחן קוד סטטוס 500 עם גוף JSON נקי, בעוד שהשגיאה המלאה תירשם בצד השרת כדי שהמפתחים יחקרו.
3. CORS (שיתוף משאבים בין מקורות)
אם יישום חזיתי מוגש מדומיין, פרוטוקול או יציאה שונים מאשר הקצה האחורי של FastAPI שלכם, דפדפנים יחסמו בקשות עקב מדיניות אותו מקור. CORS הוא המנגנון להרפיית מדיניות זו. FastAPI מספקת `CORSMiddleware` ייעודי וניתן להגדרה גבוהה למטרה זו בדיוק.
דוגמה לתצורת CORS:
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
# הגדר את רשימת המקורות המותרים. השתמש ב-"*" עבור ממשקי API ציבוריים, אך היה ספציפי יותר לאבטחה טובה יותר.
origins = [
"http://localhost:3000",
"https://my-production-frontend.com",
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True, # אפשר לעוגיות להיכלל בבקשות בין מקורות
allow_methods=["*"], # אפשר את כל שיטות ה-HTTP הסטנדרטיות
allow_headers=["*"], # אפשר את כל הכותרות
)
זהו אחד מחלקי תוכנת הביניים הראשונים שסביר להניח שתוסיפו לכל פרויקט עם חזית מנותקת, מה שמקל על ניהול מדיניות בין מקורות ממקום מרכזי יחיד.
4. דחיסת GZip
דחיסת תגובות HTTP יכולה להפחית משמעותית את גודלן, מה שמוביל לזמני טעינה מהירים יותר עבור לקוחות ועלויות רוחב פס נמוכות יותר. FastAPI כוללת `GZipMiddleware` לטיפול בכך באופן אוטומטי.
דוגמה לתוכנת ביניים GZip:
from fastapi import FastAPI
from fastapi.middleware.gzip import GZipMiddleware
app = FastAPI()
# הוסף את תוכנת הביניים GZip. באפשרותכם להגדיר גודל מינימלי לדחיסה.
app.add_middleware(GZipMiddleware, minimum_size=1000)
@app.get("/")
async def root():
# התגובה הזו קטנה ולא תידחס ב-gzip.
return {"message": "Hello World"}
@app.get("/large-data")
async def large_data():
# התגובה הגדולה הזו תידחס אוטומטית על ידי תוכנת הביניים.
return {"data": "a_very_long_string..." * 1000}
עם תוכנת ביניים זו, כל תגובה הגדולה מ-1000 בתים תידחס אם הלקוח מציין שהוא מקבל קידוד GZip (שכמעט כל הדפדפנים והלקוחות המודרניים עושים זאת).
מושגים מתקדמים ושיטות עבודה מומלצות
ככל שתהפכו למיומנים יותר בתוכנות ביניים, חשוב להבין כמה ניואנסים ושיטות עבודה מומלצות לכתיבת קוד נקי, יעיל וצפוי.
1. סדר תוכנת הביניים חשוב!
זהו הכלל החשוב ביותר שיש לזכור. תוכנת הביניים מעובדת בסדר שבו היא מתווספת לאפליקציה. תוכנת הביניים הראשונה שנוספה היא השכבה החיצונית ביותר של "הבצל".
שקלו את ההגדרה הזו:
app.add_middleware(ErrorHandlingMiddleware) # החיצוני ביותר
app.add_middleware(LoggingMiddleware)
app.add_middleware(AuthenticationMiddleware) # הפנימי ביותר
זרימת הבקשה תהיה:
ErrorHandlingMiddlewareמקבלת את הבקשה. היא עוטפת את `call_next` שלה בבלוק `try...except`.- היא קוראת ל-`next`, ומעבירה את הבקשה ל-`LoggingMiddleware`.
LoggingMiddlewareמקבלת את הבקשה, רושמת אותה וקוראת ל-`next`.AuthenticationMiddlewareמקבלת את הבקשה, מאמתת את האישורים וקוראת ל-`next`.- הבקשה מגיעה לבסוף לנקודת הקצה.
- נקודת הקצה מחזירה תגובה.
AuthenticationMiddlewareמקבלת את התגובה ומעבירה אותה הלאה.LoggingMiddlewareמקבלת את התגובה, רושמת אותה ומעבירה אותה הלאה.ErrorHandlingMiddlewareמקבלת את התגובה הסופית ומחזירה אותה ללקוח.
סדר זה הגיוני: מטפל השגיאות נמצא בחוץ כדי שיוכל לתפוס שגיאות מכל שכבה עוקבת, כולל תוכנת הביניים האחרת. שכבת האימות נמצאת עמוק בפנים, כך שאנחנו לא טורחים לרשום או לעבד בקשות שיידחו בכל מקרה.
2. העברת נתונים עם `request.state`
לפעמים, תוכנת ביניים צריכה להעביר מידע לנקודת הקצה. לדוגמה, תוכנת ביניים לאימות עשויה לפענח JWT ולחלץ את מזהה המשתמש. כיצד היא יכולה להפוך את מזהה המשתמש הזה לזמין לפונקציית פעולת הנתיב?
הדרך הלא נכונה היא לשנות את אובייקט הבקשה ישירות. הדרך הנכונה היא להשתמש באובייקט request.state. זהו אובייקט ריק ופשוט המסופק למטרה זו בדיוק.
דוגמה: העברת נתוני משתמש מתוכנת ביניים
# בשיטת השליחה של תוכנת הביניים לאימות:
# ... לאחר אימות האסימון ופענוח המשתמש ...
user_data = {"id": 123, "username": "global_dev"}
request.state.user = user_data
response = await call_next(request)
# בנקודת הקצה שלכם:
@app.get("/profile")
async def get_user_profile(request: Request):
current_user = request.state.user
return {"profile_for": current_user}
זה שומר על הלוגיקה נקייה ונמנע מזיהום מרחב השמות של אובייקט `Request`.
3. שיקולי ביצועים
בעוד שתוכנת ביניים היא עוצמתית, כל שכבה מוסיפה כמות קטנה של תקורה. עבור יישומים בעלי ביצועים גבוהים, זכרו את הנקודות הבאות:
- שמרו על זה רזה: הלוגיקה של תוכנת הביניים צריכה להיות מהירה ויעילה ככל האפשר.
- היו אסינכרוניים: אם תוכנת הביניים שלכם צריכה לבצע פעולות קלט/פלט (כגון בדיקת מסד נתונים), ודאו שהיא `async` לחלוטין כדי למנוע חסימת לולאת האירועים של השרת.
- השתמשו בזה במטרה: אל תוסיפו תוכנת ביניים שאתם לא צריכים. כל אחת מהן מוסיפה לעומק מחסנית הקריאות ולזמן העיבוד.
4. בדיקת תוכנת הביניים שלכם
תוכנת ביניים היא חלק קריטי בלוגיקה של האפליקציה שלכם ויש לבדוק אותה ביסודיות. `TestClient` של FastAPI הופך את זה לפשוט. באפשרותכם לכתוב בדיקות השולחות בקשות עם ובלי התנאים הנדרשים (לדוגמה, עם ובלי מפתח API חוקי) ולוודא שתוכנת הביניים מתנהגת כצפוי.
דוגמה לבדיקה עבור APIKeyMiddleware:
from fastapi.testclient import TestClient
from .main import app # יבא את אפליקציית FastAPI שלך
client = TestClient(app)
def test_request_without_api_key_is_forbidden():
response = client.get("/")
assert response.status_code == 403
assert response.json() == {"detail": "Forbidden: Invalid or missing API Key"}
def test_request_with_valid_api_key_is_successful():
headers = {"X-API-Key": "my-super-secret-key"}
response = client.get("/", headers=headers)
assert response.status_code == 200
assert response.json() == {"message": "Welcome to the secure zone!"}
מסקנה
תוכנת ביניים של FastAPI היא כלי בסיסי ועוצמתי עבור כל מפתח הבונה ממשקי API מודרניים. היא מספקת דרך אלגנטית וניתנת לשימוש חוזר לטפל בדאגות חוצות, ולהפריד אותן מהלוגיקה העסקית העיקרית שלכם. על ידי יירוט ועיבוד כל בקשה ותגובה, תוכנת ביניים מאפשרת לכם ליישם רישום חזק, טיפול בשגיאות מרכזי, מדיניות אבטחה מחמירה ושיפורי ביצועים כמו דחיסה.
מהמעצב הפשוט @app.middleware("http") ועד לפתרונות מתוחכמים מבוססי מחלקות, יש לכם את הגמישות לבחור את הגישה הנכונה לצרכים שלכם. על ידי הבנת מושגי הליבה, מקרי השימוש הנפוצים ושיטות העבודה המומלצות כמו סדר תוכנת ביניים וניהול מצב, תוכלו לבנות יישומי FastAPI נקיים, מאובטחים וקלים יותר לתחזוקה.
עכשיו תורכם. התחילו לשלב תוכנת ביניים מותאמת אישית בפרויקט FastAPI הבא שלכם ופתחו רמה חדשה של שליטה ואלגנטיות בעיצוב ה-API שלכם. האפשרויות הן עצומות, ושליטה בתכונה זו ללא ספק תהפוך אתכם למפתח יעיל ואפקטיבי יותר.